'''
dionysus - a web based dialogue management framework
Copyright (C) 2011  Eugeniu Costetchi <eugeniu.costetchi (at) tudor.lu>

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

Created on Jan 11, 2011
'''
from DialogueManager.Context import Context, access_context_object, \
    set_context_object
from random import sample
import os.path
from DialogueManager.utils.utils import random_string, shrink, \
    get_place_holders, iterable_to_cvs_place_holder_values, \
    substitute_place_holders, pprint
from DialogueManager.planning.EventDecorator import Event
from DialogueManager.utils.log_utils import log_debug
from DialogueManager.Parser import lowyn_type_and_tokens
from DialogueManager.planning.Operator import Operator, operand_type
from DialogueManager.utils.properties import project_path

#===============================================================================
# Action - generic class for any kind of action that is to be executed
#===============================================================================
class Action(object):
    ''' generic class for any kind of action that is to be executed.
        It is also the base class for graph nodes'''
    def __init__(self):
        self._identifier = random_string()
        self.is_breaking_execution = False
        self.succesor = None
    
    @Event
    def execute(self, context):
        ''' execute the action '''
        pass
    
    def __str__(self):
        return self.__class__.__name__ + ' () #' + self._identifier + ''
    
    def __repr__(self):
        return self.__str__()
    
#===============================================================================
# 
#===============================================================================
class InteractionAction(Action):
    def __init__(self):
        Action.__init__(self)
    
class SayInteractionAction(InteractionAction):
    
    def __init__(self, utterance=None):
        InteractionAction.__init__(self)
        self.__utterace = utterance
        self.execute += self.__say
        
    def __say(self, context):
        if self.__utterace:
            context.set_emit(self.__utterace)
            if context[Context.NEXT_MACHINE_UTTERANCE]:
                context.set_emit(context[Context.NEXT_MACHINE_UTTERANCE])
                context[Context.NEXT_MACHINE_UTTERANCE] = None
        else:
            context.set_emit('...')
            log_debug('probably will never see this message, but if you do it is quite serious', True)
        #raise Exception('Some trivial exception')

    def __str__(self):
        return self.__class__.__name__ + ' (' + shrink(self.__utterace) + ')' + ' #' + self._identifier 


class SayTemplateInteractionAction(InteractionAction):
    
    __template = None
    __place_holders_from = Context.VARIABLES
    
    def __init__(self, template, place_holders_from=Context.VARIABLES):
        InteractionAction.__init__(self)
        self.__template = template
        self.__place_holders_from = place_holders_from
        self.execute += self.__say_template
    
    def __say_template(self, context):
        #obtain a simple string to be uttered
        try:
            needed_ph = get_place_holders(self.__template)
            stripped_ph_values = iterable_to_cvs_place_holder_values(place_holdes=needed_ph, ph_pool=context[self.__place_holders_from]) 
            instance = substitute_place_holders(self.__template, stripped_ph_values)
        except:
            instance = "?_! " + self.__template
            log_debug("Could not substitute place holders", True)
        #invoke a simple say action
        SayInteractionAction(instance).execute(context)

    def __str__(self):
        return self.__class__.__name__ + ' (' + shrink(self.__template) + ')' + ' #' + self._identifier

class InputException(Exception):
    pass

class ListenInteractionAnction(InteractionAction):
    
    __input_type = None
    __remember_as_variable = None
    
    def __init__(self, remember_as_variable=None, input_type=None):
        InteractionAction.__init__(self)
        self.is_breaking_execution = True
        self.execute += self.__listen
        self.__input_type = input_type
        self.__remember_as_variable = remember_as_variable
        
    def __listen(self, context):    
        message = context.get_receive()
        atype, atokens = self.__analyse(message, context)
        
        context[Context.DICTIONARY].add_words(atokens)
        context[Context.LAST_USER_UTTERANCE][Context.ANALYSED_TYPE] = atype
        context[Context.LAST_USER_UTTERANCE][Context.ANALYSED_TOKENS] = atokens
        
        if self.__remember_as_variable:
            set_context_object(self.__remember_as_variable, atokens, context)
        
        if self.__input_type is not None:
            if not self.__input_type == atype:
                raise InputException('The expected input type is ' + self.__input_type + ' type instead received ' + atype + ' type')
    
    def __analyse(self, message, context):
#        if message is None or not message:
#            raise  InputException('Perceived input is null or Empty')
#        try:
        low_type, tokens = lowyn_type_and_tokens(string=message, context=context)
#        except Exception, e:
#            raise PlanningException('Could not analyze input because ->' + str(e))
        return low_type, tokens


    def __str__(self):
        if self.__remember_as_variable is not None:
            return self.__class__.__name__ + ' (' + shrink(self.__remember_as_variable, 12, True) + ')' + ' #' + self._identifier
        else: 
            return self.__class__.__name__ + ' (' + ')' + ' #' + self._identifier

class AskQafQuestion(InteractionAction):
    def __init__(self, qaf_instance=None):
        InteractionAction.__init__(self)
        self.execute += self.__askq
        self.__qaf_insance = qaf_instance
        
    def __askq(self, context):
        if self.__qaf_insance is None:
            utt = context[Context.QAF_INSTANCES][-1].question()
        else:
            utt = self.__qaf_insance.question()
        say = SayInteractionAction(utt)
        say.execute(context)
        
#===============================================================================
# 
#===============================================================================
class PlanControls(Action):
    def __init__(self):
        Action.__init__(self)

class PointerControl(PlanControls):
    def __init__(self, id=None):
        PlanControls.__init__(self)
        if id is not None: 
            self._identifier = id

class StartPointer(PointerControl):
    ''' plan begining'''
    def __init__(self):
        PointerControl.__init__(self)
         
class StopPointer(PointerControl):
    ''' plan end '''
    def __init__(self):
        PointerControl.__init__(self)
        self.is_breaking_execution = True
        self.succesor = self

class BeginPointer(PointerControl):
    ''' marks the begining of a fragment'''
    def __init__(self):
        PointerControl.__init__(self)
        
class EndPointer(PointerControl):
    ''' marks the end of a fragment'''
    def __init__(self):
        PointerControl.__init__(self)

#===============================================================================
# 
#===============================================================================
class IfControl(PlanControls):
    
    def __init__(self, condition_operator, end_if, then_next_action, else_next_action=None):
        PlanControls.__init__(self)
        assert isinstance(condition_operator, Operator)
        assert isinstance(then_next_action, Action)
        assert isinstance(end_if, EndIfControl)
        if else_next_action is not None:
            assert isinstance(else_next_action, Action)
        self.condition_operator = condition_operator
        self.end_if = end_if
        self.then_successor = then_next_action
        self.else_successor = else_next_action
        
        self.execute += self.__eval_cond_and_set_succesor 
    
    def __eval_cond_and_set_succesor(self, context):
        self.condition_operator.evaluate(context)
        if self.condition_operator.result:
            self.succesor = self.then_successor
        else:
            if self.else_successor is not None:
                self.succesor = self.else_successor
            else:
                self.succesor = self.end_if

    def __str__(self):
        return self.__class__.__name__ + ' ' + str(self.condition_operator) + ' ' + '#' + self._identifier

                
class EndIfControl(PlanControls):

    def __init__(self, if_control=None):
        PlanControls.__init__(self)
        if if_control is not None:
            assert isinstance(if_control, IfControl)
        self.if_control = if_control

class WhileControl(PlanControls):
    def __init__(self, condition, end_while, next_loop_action):
        PlanControls.__init__(self)
        assert isinstance(condition, Operator)
        assert isinstance(end_while, EndLoopControl)
        assert isinstance(next_loop_action, Action)
        self.condition = condition
        self.end_while = end_while
        self.next_loop_action = next_loop_action
        self.succesor = self.next_loop_action
    
        self.execute += self.__iterate
        
    def __iterate(self, context):
        self.condition.evaluate(context)
        if self.condition.result: 
            self.succesor = self.next_loop_action
            self.end_while.repeat = True
        else: 
            self.succesor = self.end_while
            self.end_while.repeat = False

    def __str__(self):
        return self.__class__.__name__ + ' ' + str(self.condition) + ' ' + '#' + self._identifier

        
class ForEachControl(PlanControls):
    
    def __init__(self, iterator_ctx_variable, iterable_list_ctx_variable, end_for, next_loop_action):
        PlanControls.__init__(self)
        assert operand_type(iterable_list_ctx_variable) == Context
        assert operand_type(iterable_list_ctx_variable) == Context
        assert isinstance(end_for, EndLoopControl)
        assert isinstance(next_loop_action, Action)
        
        self.__iteration_variable_ctx = iterator_ctx_variable
        self.__iterated_list_ctx = iterable_list_ctx_variable
        self.end_for = end_for
        self.next_loop_action = next_loop_action
        self.succesor = next_loop_action
        self.iterated_value = None
        self.iterated_list = None
        self.__iterator = None
        
        self.execute += self.__iterate
    
    def __iterate(self, context):
        #assume normal flow
        self.end_for.repeat = True
        #check init
        if self.__iterator is None:
            if not self.__init_iterator(context):
                self.__stop_iteration()
                log_debug('Loop did not initialize')
                return 
        #taking next value
        try:
            self.iterated_value = self.__iterator.next()
            set_context_object(self.__iteration_variable_ctx, self.iterated_value, context)
        except StopIteration:
            self.__stop_iteration()
        
    def __init_iterator(self, context):
        self.succesor = self.next_loop_action
        self.iterated_list = access_context_object(self.__iterated_list_ctx, context)
        try:
            self.__iterator = iter(self.iterated_list)
        except TypeError:
            log_debug('iterator failed to initialize because ' + self.iterated_list + ' is not iterable', True)
            return False
        return True
        
    def __reset(self):
        self.iterated_value = None
        self.iterated_list = None
        self.__iterator = None
        
    def __stop_iteration(self):
        self.succesor = self.end_for
        self.end_for.repeat = False
        self.__reset()

    def __str__(self):
        return self.__class__.__name__ + ' ' + shrink(self.__iteration_variable_ctx, 12, True) + ' in ' + shrink(self.__iterated_list_ctx, 12, True) + '#' + self._identifier

class EndLoopControl(PlanControls):
    ''' marks the end of the loop and indicated whether to 
        evaluate condition again and to loop or to exit'''
    def __init__(self, for_control=None):
        PlanControls.__init__(self)
        if for_control is not None:
            assert isinstance(for_control, IfControl)
        self.loop_control = for_control
        self.repeat = True
        self.execute += self.__exec
        
    def __exec(self, context):
        if self.repeat:
            self.succesor = self.loop_control
        else:
            self.succesor = None

#===============================================================================
# 
#===============================================================================
class InternalAction(Action):
    def __init__(self):
        Action.__init__(self)

class InstantiateQaf(InternalAction):
    
    def __init__(self, qaf_template, place_holders):
        InternalAction.__init__(self)
        self.execute += self.__inst
        self.__qaf_aboutness = qaf_template
        self.__place_holders = place_holders

    def __inst(self, context):
        i = self.__qaf_aboutness.instantiate(self.__place_holders)
        context[Context.QAF_INSTANCES].append(i)


class InstantiateQafFromTemplatePool(InternalAction):
    
    def __init__(self, qaf_aboutness, place_holders_var=None):
        assert qaf_aboutness
        InternalAction.__init__(self)
        self.__qaf_aboutness = qaf_aboutness
        self.__place_holders_var = place_holders_var
        self.__place_holders = None 
        self.execute += self.__inst

    def __inst(self, context):
        # init placeholders
        if self.__place_holders_var is None:
            self.__place_holders = context[Context.VARIABLES]
        else: 
            self.__place_holders = access_context_object(self.__place_holders_var, context)
        #get a random instance of template from context
        template = sample(context[Context.QAF_TEMPLATES][self.__qaf_aboutness], 1)[0]
        # instantiate
        i = template.instantiate(self.__place_holders)
        context[Context.QAF_INSTANCES].append(i)

    def __str__(self):
        return self.__class__.__name__ + ' ' + shrink(self.__qaf_aboutness) + ' ' + '#' + self._identifier
    
class AcceptAnswer(InternalAction):
    
    def __init__(self, qaf_instance=None):
        InternalAction.__init__(self)
        self.__qaf_instance = qaf_instance
        self.execute += self.__aa
        
    def __aa(self, context):
        # if no instance provided take the top qaf_instance from the context
        if self.__qaf_instance is None:
            self.__qaf_instance = context[Context.QAF_INSTANCES][-1] 
        
        # take last user utterance and inject it into instance
        self.__qaf_instance.inject_answer(context[Context.LAST_USER_UTTERANCE][Context.ANALYSED_TYPE],
                                          context[Context.LAST_USER_UTTERANCE][Context.ANALYSED_TOKENS])

class AccomodateQafFormalExpression(InternalAction):
    def __init__(self, qaf_instance=None):
        InternalAction.__init__(self)
        self.__qaf_instance = qaf_instance
        self.execute += self.__acc
        
    def __acc(self, context):
        # if no instance provided take the top qaf_instance from the context
        if self.__qaf_instance is None:
            self.__qaf_instance = context[Context.QAF_INSTANCES][-1]
        context[Context.FORMAL_CODE] += '\n' + self.__qaf_instance.formal_code()

class EradicateQaf(InternalAction):
    def __init__(self, qaf_instance=None):
        InternalAction.__init__(self)
        self.__qaf_instance = qaf_instance
        self.execute += self.__acc
        
    def __acc(self, context):   
        # if no instance provided take the top qaf_instance from the context
        if self.__qaf_instance is None:
            self.__qaf_instance = context[Context.QAF_INSTANCES][-1]
            #remove the last instance 
            del (context[Context.QAF_INSTANCES][-1])
        elif self.__qaf_instance == context[Context.QAF_INSTANCES][-1]:
            #still remove last instance from context if is equal to the provided one
            del (context[Context.QAF_INSTANCES][-1])

class FormalizeGroupsToTurtleTriples(InternalAction):
    def __init__(self):
        InternalAction.__init__(self)
        self.execute += self.__transform
    
    def __transform(self, context):
        res = '\n'
        p = context[Context.VARIABLES][Context.GROUPS]
        for gt in p:
            for w in p[gt]:
                res+= ':'+w.replace(' ','_')+' inton:inGroup :'+gt.replace(' ','_')+'. \n'
        context[Context.FORMAL_CODE]+=res
            
class DumpContext(InternalAction):
    def __init__(self):
        InternalAction.__init__(self)
        self.execute += self.__dump
    
    def __handle_namespaces(self, context):
        try:
            name = domain = access_context_object('@ctx.variables.name', context)[0]
        except:
            name = 'noName'
        try:
            domain = domain = access_context_object('@ctx.variables.domain_topic', context)[0]
        except:
            domain = 'no_domain'
        res = '@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .\n\
@prefix xml: <http://www.w3.org/XML/1998/namespace> .\n\
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .\n\
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n\
@prefix owl: <http://www.w3.org/2002/07/owl#> .\n\
@prefix inton: <http://tudor.lu/InterviewOntology#> .\n\
@prefix : <http://tudor.lu/' + name + '/' + domain + '/#> .\n\
@base <http://tudor.lu/' + name + '/' + domain + '> .'
        return res
    
    def __dump(self, context):
        log_debug('\nStart Context Dump~~~~~~~~~~~~~~~~~~~~~~\n' + 
                  str(context) + 
                  '\nStop Context Dump~~~~~~~~~~~~~~~~~~~~~~\n', exception=False)
        
        domain = 'unknown'
        try:
            domain = access_context_object('@ctx.variables.domain_topic', context)[0]
        except: pass
        try:
            dir_name = project_path + '/outputs/' + str(domain) + '/'
            if not os.path.exists(dir_name):
                os.makedirs(dir_name)
            #triples
            fname = dir_name + 'triples.ttl'
            file = open(fname, 'w')
            file.write(self.__handle_namespaces(context))
            file.write(str(context[Context.FORMAL_CODE]))
            file.close()
            #variables
            fname = dir_name + 'variables.ctx'
            file = open(fname, 'w')
            file.write(pprint(context[Context.VARIABLES]))
            file.close()
            #dict
            fname = dir_name + 'dictionary.ctx'
            file = open(fname, 'w')
            file.write(pprint(context[Context.DICTIONARY]))
            file.close()
            #ctx all
            fname = dir_name + 'all.ctx'
            file = open(fname, 'w')
            file.write(pprint(context))
            file.close()
        except:
            log_debug("Could not write to file: " + fname, exception=True)

class SetVariableToContext(InternalAction):
    def __init__(self, variable, value):
        InternalAction.__init__(self)
        self.execute += self.__set
        self.__variable = variable
        self.__value = value
        
    def __set(self, context):
#        if self.__key is not None:
#            set_context_object(self.__variable, {self.__key:self.__value}, context)
#        else:
        set_context_object(self.__variable, self.__value, context)
        
    def __str__(self):
        return self.__class__.__name__ + '(' + shrink(self.__variable, 12, True) + ', ' + shrink(self.__value, 12, True) + ') #' + self._identifier
                    
class UpdateDictInContext(InternalAction):
    def __init__(self, dict_var, key_var, value_var):
        InternalAction.__init__(self)
        self.execute += self.__set
        self.__variable = dict_var
        self.__value = value_var
        self.__key = key_var
        
    def __set(self, context):
        k = access_context_object(self.__key, context)
        if len(k) > 1:
            log_debug('WARNING, Group title is more than two words, using just the 1st one')
        k = k[0]
        v = access_context_object(self.__value, context)
        set_context_object(self.__variable, {k: v}, context)
    
    def __str__(self):
        return self.__class__.__name__ + '(' + str(self.__variable) + ', ' + str(self.__key) + ', ' + str(self.__value) + ') #' + self._identifier
                
class CopyVariable(InternalAction):
    def __init__(self, var1, var2, move=False):
        InternalAction.__init__(self)
        self.execute += self.__cp
        self.__var1 = var1
        self.__var2 = var2 
        self.__move = move
        
    def __cp(self, context):
        set_context_object(self.__var2, access_context_object(self.__var1, context), context)
        if self.__move:
            set_context_object(self.__var1, None, context)

    def __str__(self):
        return self.__class__.__name__ + '(' + shrink(self.__var1, 12, True) + ', ' + shrink(self.__var2, 12, True) + ') #' + self._identifier

#class GetVariableFromContext(InternalAction):
#    def __init__(self,variable):
#        InternalAction.__init__(self)
#        self.execute += self.__get 
#
#    def __get(self, context):
#        context

#def method1(context):
#    print 'method1'
#
#def method2(context):
#    print 'method2'
#
#a = Action()
#a.execute+= method2
#a.execute+= method1
#a.execute(None)
#
#class ChildAction(Action):
#    def __init__(self):
#        self.execute+=method2
#
#
#b = ChildAction()
#b.execute(None)


